#include "engine.h"
#include "internal_types.h"

#include <GL/glew.h>
#include <set>
using namespace std;

namespace HW
{

using namespace Resource;

static set<TBuffer> lBuffers;
static TBuffer lCurrentBuffer;
extern TBuffer lBackBuffer;
static GLenum* lFboBuffs;

TBuffer CreateBuffer(int width, int height, int flags)
{
	AppLog ( "Creating buffer." );
	_i_Buffer* lNewBuf = new _i_Buffer();

	lNewBuf -> width = width;
	lNewBuf -> height = height;

	glGenFramebuffers ( 1 , &lNewBuf->id );

	lNewBuf -> colTex[0] = NULL;
	lNewBuf -> colTex[1] = NULL;
	lNewBuf -> colTex[2] = NULL;
	lNewBuf -> colTex[3] = NULL;
	lNewBuf -> depTex = NULL;
	lNewBuf -> drawBuffsNum = 0;

	if ( (flags&BUFFER_DEPTH) != 0 )
		SetDepthBuffer ( (TBuffer)(lNewBuf) , CreateTexture ( width , height , TEXTURE_DEPTH24 ) );

	if ( (flags&BUFFER_COLOR0) != 0 )
		SetColorBuffer ( (TBuffer)(lNewBuf) , CreateTexture ( width , height , TEXTURE_RGBA8 ) , 0 );
	if ( (flags&BUFFER_COLOR1) != 0 )
		SetColorBuffer ( (TBuffer)(lNewBuf) , CreateTexture ( width , height , TEXTURE_RGBA8 ) , 1 );
	if ( (flags&BUFFER_COLOR2) != 0 )
		SetColorBuffer ( (TBuffer)(lNewBuf) , CreateTexture ( width , height , TEXTURE_RGBA8 ) , 2 );
	if ( (flags&BUFFER_COLOR3) != 0 )
		SetColorBuffer ( (TBuffer)(lNewBuf) , CreateTexture ( width , height , TEXTURE_RGBA8 ) , 3 );

	lBuffers.insert ( (TBuffer)(lNewBuf) );

	return (TBuffer)(lNewBuf);
}

TBuffer BackBuffer(void)
{
	return lBackBuffer;
}

int BufferWidth(TBuffer buffer)
{
	return ((_i_Buffer*)(buffer)) -> width;
}

int BufferHeight(TBuffer buffer)
{
	return ((_i_Buffer*)(buffer)) -> height;
}

void SetBuffer(TBuffer buffer)
{
	glBindFramebuffer ( GL_DRAW_FRAMEBUFFER , ((_i_Buffer*)(buffer))->id );

	if ( ((_i_Buffer*)(buffer))->id == 0 ) //It's the back buffer :)
		glDrawBuffer(GL_BACK);
	else
		glDrawBuffers(((_i_Buffer*)(buffer))->drawBuffsNum, lFboBuffs);

	lCurrentBuffer = buffer;
}

int MaxColorBuffers(void)
{
	GLint lQuery;
	glGetIntegerv ( GL_MAX_COLOR_ATTACHMENTS , &lQuery );
	return lQuery;
}

TTexture GetColorBuffer(TBuffer buffer, int n)
{
	return ((_i_Buffer*)(buffer))->colTex[n];
}

TTexture GetDepthBuffer(TBuffer buffer)
{
	return ((_i_Buffer*)(buffer))->depTex;
}

TBuffer CreateCustomBuffer(byte* GetSize, byte* MakeCurrent)
{
	return NULL;
}

int SaveBuffer(TBuffer buffer, const_str name, int quality)
{
	return 0;
}

void ClearBuffer(int clsmode)
{
	GLenum lBuffMask[4];
	lBuffMask[0] = ((clsmode&BUFFER_COLOR0)>0)?GL_COLOR_ATTACHMENT0:GL_NONE;
	lBuffMask[1] = ((clsmode&BUFFER_COLOR1)>0)?GL_COLOR_ATTACHMENT1:GL_NONE;
	lBuffMask[2] = ((clsmode&BUFFER_COLOR2)>0)?GL_COLOR_ATTACHMENT2:GL_NONE;
	lBuffMask[3] = ((clsmode&BUFFER_COLOR3)>0)?GL_COLOR_ATTACHMENT3:GL_NONE;
	glDrawBuffers(4, lBuffMask);
	GLbitfield lBF = GL_COLOR_BUFFER_BIT;
	if ( (clsmode&BUFFER_DEPTH) != 0 )
		lBF |= GL_DEPTH_BUFFER_BIT;
	glClear ( lBF );
	SetBuffer ( CurrentBuffer() );
}

void FreeBuffer(TBuffer buffer)
{
	AppLog ( "Freeing buffer." );

	glDeleteFramebuffers ( 1 , &((_i_Buffer*)(buffer))->id );

	if ( GetDepthBuffer ( buffer ) != NULL )
		FreeTexture ( GetDepthBuffer ( buffer ) );

	for ( int i = 0 ; i < 4 ; i ++ )
	{
		if ( GetColorBuffer ( buffer , i ) != NULL )
			FreeTexture ( GetColorBuffer ( buffer , i ) );
	}

	lBuffers.erase ( buffer );
	delete buffer;
}

TBuffer CurrentBuffer(void)
{
	return lCurrentBuffer;
}

void CopyBuffer(TBuffer src, TBuffer dst, int components)
{
	glBindFramebuffer ( GL_READ_FRAMEBUFFER , ((_i_Buffer*)(src))->id );
	TBuffer lCurrBuff = CurrentBuffer();
	SetBuffer ( dst );

	for ( int buffNum = 0 ; buffNum < 4 ; buffNum ++ )
	{
		if ( 
			( (components & (int)(BUFFER_COLOR0*pow(2.0f,buffNum)) ) != 0 ) &&
			( ((_i_Buffer*)(src))->colTex[buffNum] != NULL )
			)
		{
			GLenum buff = GL_COLOR_ATTACHMENT0 + buffNum;
			if ( ((_i_Buffer*)(dst))->id != 0 )
				glDrawBuffer(buff);
			else
				glDrawBuffer(GL_BACK);
			glReadBuffer(buff);

			glBlitFramebuffer ( 0 , 0 , ((_i_Buffer*)(src))->width , ((_i_Buffer*)(src))->height ,
								0 , 0 , ((_i_Buffer*)(dst))->width , ((_i_Buffer*)(dst))->height ,
								GL_COLOR_BUFFER_BIT , GL_LINEAR );
		}
	}

	if ( (components&BUFFER_DEPTH) != 0 )
	{
		glBlitFramebuffer ( 0 , 0 , ((_i_Buffer*)(src))->width , ((_i_Buffer*)(src))->height ,
							0 , 0 , ((_i_Buffer*)(dst))->width , ((_i_Buffer*)(dst))->height ,
							GL_DEPTH_BUFFER_BIT , GL_NEAREST );
	}

	SetBuffer ( lCurrBuff );
}

int SetColorBuffer(TBuffer buffer, TTexture texture, int index, int cubeface)
{
	TBuffer lCurBuf = CurrentBuffer();

	if ( GetColorBuffer ( buffer , index ) != NULL )
		FreeTexture ( GetColorBuffer ( buffer , index ) );

	SetBuffer ( buffer );
	glFramebufferTexture2D ( GL_DRAW_FRAMEBUFFER , GL_COLOR_ATTACHMENT0+index , GL_TEXTURE_2D , ((_i_Texture*)(texture))->id , 0 );
	((_i_Buffer*)(buffer))->colTex[index] = texture;
	((_i_Buffer*)(buffer))->drawBuffsNum = max ( index+1 , ((_i_Buffer*)(buffer))->drawBuffsNum );

	SetBuffer ( lCurBuf );

	return 1;
}

int SetDepthBuffer(TBuffer buffer, TTexture texture)
{
	TBuffer lCurBuf = CurrentBuffer();

	if ( GetDepthBuffer ( buffer ) != NULL )
		FreeTexture ( GetDepthBuffer ( buffer ) );

	SetBuffer ( buffer );
	glFramebufferTexture2D ( GL_DRAW_FRAMEBUFFER , GL_DEPTH_ATTACHMENT , GL_TEXTURE_2D , ((_i_Texture*)(texture))->id , 0 );
	((_i_Buffer*)(buffer))->depTex = texture;

	SetBuffer ( lCurBuf );

	return 1;
}

void _i_InitBuffers ( void )
{
	_i_Buffer* lbb = new _i_Buffer();
	lbb -> id = 0;
	lbb -> width = GraphicsWidth();
	lbb -> height = GraphicsHeight();
	lbb -> colTex[0] = NULL;
	lbb -> colTex[1] = NULL;
	lbb -> colTex[2] = NULL;
	lbb -> colTex[3] = NULL;
	lbb -> depTex = NULL;
	lBackBuffer = (TBuffer)(lbb);

	lCurrentBuffer = BackBuffer();

	lFboBuffs = new GLenum[4];
	lFboBuffs[0] = GL_COLOR_ATTACHMENT0;
	lFboBuffs[1] = GL_COLOR_ATTACHMENT1;
	lFboBuffs[2] = GL_COLOR_ATTACHMENT2;
	lFboBuffs[3] = GL_COLOR_ATTACHMENT3;
}

void _i_UnInitBuffers ( void )
{
	while ( !lBuffers.empty() )
		FreeBuffer ( *lBuffers.begin() );
}

}